package com.caipos.actor.app;

import com.caipos.actor.ActorSystemContext;
import com.caipos.actor.common.actors.*;
import com.caipos.actor.common.data.EntityType;
import com.caipos.actor.common.data.Tenant;
import com.caipos.actor.common.data.id.TenantId;
import com.caipos.actor.common.msg.KbActorMsg;
import com.caipos.actor.common.msg.MsgType;
import com.caipos.actor.service.ContextAwareActor;
import com.caipos.actor.service.ContextBasedCreator;
import com.caipos.actor.service.DefaultActorService;
import com.caipos.actor.tenant.TenantActor;
import com.caipos.service.TenantService;
import lombok.extern.slf4j.Slf4j;

import java.util.*;

@Slf4j
public class AppActor extends ContextAwareActor {

    private final TenantService tenantService;
    private final Set<TenantId> deletedTenants;
    private volatile boolean ruleChainsInitialized;

    private AppActor(ActorSystemContext systemContext) {
        super(systemContext);
        this.tenantService = systemContext.getTenantService();
        this.deletedTenants = new HashSet<>();
    }

    @Override
    public void init(KbActorCtx ctx) throws KbActorException {
        super.init(ctx);
    }

    @Override
    protected boolean doProcess(KbActorMsg msg) {
        if (!ruleChainsInitialized) {
            if (MsgType.APP_INIT_MSG.equals(msg.getMsgType())) {
                initTenantActors();
                ruleChainsInitialized = true;
            } else {
                if (!msg.getMsgType().isIgnoreOnStart()) {
                    log.warn("Attempt to initialize Rule Chains by unexpected message: {}", msg);
                }
                return true;
            }
        }
        switch (msg.getMsgType()) {
            case APP_INIT_MSG:
                break;
            case PARTITION_CHANGE_MSG:
                ctx.broadcastToChildren(msg, true);
                break;
            case SESSION_TIMEOUT_MSG:
                ctx.broadcastToChildrenByType(msg, EntityType.TENANT);
                break;
            default:
                return false;
        }
        return true;
    }

    private void initTenantActors() {
        log.info("Starting main system actor.");
        try {
            if (systemContext.isTenantComponentsInitEnabled()) {
                List<Tenant> allTenants = tenantService.getAllTenants();
                for (Tenant tenant : allTenants) {
                    log.debug("[{}] Creating tenant actor", tenant.getId());
                    getOrCreateTenantActor(new TenantId(tenant.getId())).ifPresentOrElse(tenantActor -> {
                        log.debug("[{}] Tenant actor created.", tenant.getId());
                    }, () -> {
                        log.debug("[{}] Skipped actor creation", tenant.getId());
                    });
                }

            }
            log.info("Main system actor started.");
        } catch (Exception e) {
            log.warn("Unknown failure", e);
        }
    }

    private Optional<KbActorRef> getOrCreateTenantActor(TenantId tenantId) {
        if (deletedTenants.contains(tenantId)) {
            return Optional.empty();
        }
        return Optional.ofNullable(ctx.getOrCreateChildActor(new KbEntityActorId(tenantId),
                () -> DefaultActorService.TENANT_DISPATCHER_NAME,
                () -> new TenantActor.ActorCreator(systemContext, tenantId),
                () -> true));
    }

    @Override
    public ProcessFailureStrategy onProcessFailure(KbActorMsg msg, Throwable t) {
        log.error("Failed to process msg: {}", msg, t);
        return doProcessFailure(t);
    }

    public static class ActorCreator extends ContextBasedCreator {

        public ActorCreator(ActorSystemContext context) {
            super(context);
        }

        @Override
        public KbActorId createActorId() {
            return new KbEntityActorId(TenantId.SYS_TENANT_ID);
        }

        @Override
        public KbActor createActor() {
            return new AppActor(context);
        }
    }

}
